using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Phoenix.ActionResults;
using Phoenix.Filters;

namespace Phoenix.Framework
{
    public class ReflectedControllerDescriptor : IControllerDescriptor
    {
        readonly ActionResolver selector;

        public ReflectedControllerDescriptor(Type controllerType)
        {
            ControllerType = controllerType;
            selector = new ActionResolver();
        }

        public Type ControllerType { get; private set; }

        public IActionDescriptor FindAction(ControllerActionContext controllerActionContext)
        {
            if (controllerActionContext == null)
                throw new ArgumentNullException("controllerActionContext");

            var matched = selector.ResolveFor(controllerActionContext, ControllerType);
            if (matched == null)
            {
                return null;
            }

            if (IsAsyncAction(matched.MethodInfo))
                return new ReflectedTaskActionDescriptor(matched, this);

            if (IsAction(matched.MethodInfo))
                return new ReflectedActionDescriptor(matched, this);

            throw new InvalidOperationException("Controller Actions must return ActionResult or Task<ActionResult>");
        }

        public IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
        {
            if (useCache && GetType() == typeof(ReflectedControllerDescriptor))
            {
                // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
                return ReflectedAttributeCache.GetTypeFilterAttributes(ControllerType);
            }

            return new FilterAttribute[0];
        }

        static bool IsAction(MethodInfo matched)
        {
            return typeof(ActionResult).IsAssignableFrom(matched.ReturnType);
        }

        static bool IsAsyncAction(MethodInfo matched)
        {
            var returnType = matched.ReturnType;

            return typeof(Task).IsAssignableFrom(returnType) &&
                returnType.IsGenericType &&
                typeof(ActionResult).IsAssignableFrom(returnType.GetGenericArguments()[0]);
        }
    }
}